package cn.fornever.tools.encrypt;

import cn.fornever.tools.encrypt.assist.HMACAlgorithms;
import cn.fornever.tools.encrypt.assist.MessageDigestAlgorithms;

import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
 * 加密工具类.
 *
 * @author ZhangAY
 */
public class EncryptUtil {

    //避免工具类实例化
    private EncryptUtil() {
    }

    private static char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};


    /**
     * 使用指定的MD算法(消息摘要算法),对{@code str}进行加密.
     *
     * <P>加密结果不可逆.
     *
     * <p>实现细节:本方法是基于JDK提供的 {@link java.security.MessageDigest},对其进行了进一步的封装,
     * 可以最大程度的避免异常的出现,降低使用难度.
     *
     *
     * @param algorithmName 算法名称
     * @param encryptText   要加密的字符串
     * @param textEncoding  {@code encryptText}的字符集.如果为 {@code null} 则采用平台默认字符集进行解析.
     * @return 加密结果.如果加密失败,则返回 {@code null}
     * @see MessageDigestAlgorithms
     * @since 1.5
     */
    public static String encrypt(MessageDigestAlgorithms algorithmName, String encryptText, String textEncoding) {
        MessageDigest md;
        String outStr = null;
        try {
            md = MessageDigest.getInstance(algorithmName.toString());
            byte[] digest = md.digest(textEncoding == null ? encryptText.getBytes() : encryptText.getBytes(textEncoding));
            outStr = byteToString(digest);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return outStr;
    }


    /**
     * 使用指定的HAMC算法对{@code str}进行加密.
     *
     * <p>加密结果不可逆
     *
     * @param algorithmName 加密算法
     * @param encryptText   要加密的字符串
     * @param encryptKey    密钥
     * @param textEncoding  {@code encryptText}的字符集.如果为 {@code null} 则采用平台默认字符集进行解析.
     * @return 加密结果.如果加密失败,则返回 {@code null}
     * @see HMACAlgorithms
     * @since 1.5
     */
    public static String encrypt(HMACAlgorithms algorithmName, String encryptText, String encryptKey,  String textEncoding) {
        String result = null;
        try {
            byte[] data = encryptKey.getBytes(textEncoding);
            //根据给定的字节数组构造一个密钥,第二参数指定一个密钥算法的名称
            SecretKey secretKey = new SecretKeySpec(data, algorithmName.toString());
            //生成一个指定 Mac 算法 的 Mac 对象
            Mac mac = Mac.getInstance(algorithmName.toString());
            //用给定密钥初始化 Mac 对象
            mac.init(secretKey);
            byte[] text = textEncoding == null ? encryptText.getBytes() : encryptText.getBytes(textEncoding);
            //完成 Mac 操作
            result = byteToString(mac.doFinal(text));
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 将指定的 {@code byte} 数组转换为字符串.
     * @param digest 指定的 {@code byte} 数组
     * @return  {@code byte} 转换成的字符串
     */
    private static String byteToString(byte[] digest) {
        int j = digest.length;
        char s[] = new char[j * 2];
        int k = 0;
        for (int i = 0; i < j; i++) {
            byte byte0 = digest[i];
            s[k++] = hexDigits[byte0 >>> 4 & 0xf];
            s[k++] = hexDigits[byte0 & 0xf];
        }
        return String.valueOf(s);
    }

}

